package cn.com.idmy.auth.oauth;

import cn.com.idmy.auth.Credential;
import cn.com.idmy.auth.Device;
import cn.com.idmy.auth.context.Request;
import cn.com.idmy.auth.context.Response;
import cn.com.idmy.auth.exception.AuthException;
import cn.com.idmy.auth.login.LoginState;
import cn.com.idmy.base.util.Assert;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.dromara.hutool.core.text.StrUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;


@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(fluent = true)
public class OauthPasswordImpl<ID> extends AbstractAccessToken<ID> {
    @Override
    protected @NotNull AccessToken getToken(@NotNull Request request, @NotNull Response response) {
        var in = getPassword(request);
        if (in.device == null) {
            in.device = Device.DEFAULT;
        }
        var credential = new Credential<>();
        credential.accountType(in.accountType);
        credential.username(in.username);
        credential.password(in.password);
        credential.captcha(in.state);
        credential.device(in.device);

        var principal = oauthManager.authenticator.authenticate(request, credential);
        var id = principal.id();

        var state = new LoginState<>(id);
        state.accountType(in.accountType());
        state.principal(principal);
        state.device(in.device);

        oauthManager.loginManager.login(state);

        var cfg = state.config();
        return new AccessToken()
                .accessToken(state.token())
                .exp(cfg.tokenExp().toSeconds())
                .refreshToken(state.refreshToken())
                .refreshExp(cfg.refreshTokenExp().toSeconds())
                .idToken(state.idToken());
    }

    private @NotNull Password getPassword(@NotNull Request request) {
        var in = request.params().toJavaObject(Password.class);
        if (StrUtil.isBlank(in.clientId)) {
            var clientInfo = getClientInfoFromAuthorization(request.authorization());
            in.clientId = clientInfo.getLeft();
            in.clientSecret = clientInfo.getRight();
        }

        var oauthClient = oauthManager.oauthClientDao.getOauthClient(in.clientId);
        in.accountType = oauthClient.getAccountType();
        var grantTypes = Assert.notEmpty(oauthClient.getGrantTypes(), "grantTypes 未配置");
        if (!grantTypes.contains(GrantType.PASSWORD.name())) {
            throw new AuthException("grant_type 不在允许的 grantTypes 列表中");
        }

        var origin = request.origin();
        if (StrUtil.isBlank(in.redirectUri)) {
            in.redirectUri = origin;
            if (StrUtil.isBlank(in.redirectUri)) {
                in.redirectUri = request.url();
            }
        }
        var redirectUris = Assert.notEmpty(oauthClient.getRedirectUris(), "redirectUris 未配置");
        if (StrUtil.isNotBlank(origin)) {
            var originDomain = Assert.notNull(getDomainFromUri(origin), "redirect_uri 的域名无效");
            Assert.isTrue(redirectUris.stream().map(this::getDomainFromUri).anyMatch(domain -> Objects.equals(domain, originDomain)), "origin 不在允许的 redirectUris 列表中");
        }
        var redirectUri = Assert.notBlank(in.redirectUri, "redirect_uri 不能为空");
        var redirectUriDomain = Assert.notNull(getDomainFromUri(redirectUri), "redirect_uri 的域名无效");
        Assert.isTrue(redirectUris.stream().map(this::getDomainFromUri).anyMatch(domain -> Objects.equals(domain, redirectUriDomain)), "redirect_uri 不在允许的 redirectUris 列表中");
        return in;
    }

    protected @Nullable String getDomainFromUri(@NotNull String uri) {
        try {
            return new URI(uri).getHost();
        } catch (URISyntaxException e) {
            return null;
        }
    }
}
